package com.hyzs.utils;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.neo4j.driver.v1.Record;
import org.neo4j.driver.v1.Session;
import org.neo4j.driver.v1.StatementResult;
import java.util.*;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

public class Neo4jFunctionUtils {

    public static List<String> getAvailableLabels(){
        return ImmutableList.copyOf(LabelKeyWords.isAllLabels.keySet());
    }

    public static Map<String, Object> generateLawMap(final Session session, Record record){
        Map<String, Object> resMap = new HashMap<>();
        Map<String, Object> lawMap = new HashMap<>();
        lawMap.putAll(record.get("n").asNode().asMap());
        StringUtils.updateLawMap(lawMap);
        resMap.put("law", lawMap);
        if(record.get("level").asInt() < 2) {
            resMap.put("children_laws", getBelongsLaw(session, lawMap.get("name").toString()));
        }
        return resMap;
    }


    public static List<Map<String, Object>> getBelongsLaw(final Session session, String name){
        String sql = " MATCH (l:Law{name: $name}) <-[:BELONGS]- (n:Law) RETURN n, n.level as level ORDER BY id(n)";
        StatementResult statementResult = session.run(sql, ImmutableMap.of("name", name));
        return statementResult.stream()
                .map(record -> generateLawMap(session, record))
                .collect(Collectors.toList());
    }

    public static Map<String, Object> genQueryLabelCode(final Session session,String doc_id, String crime){

        Map<String, Object> resMap = new HashMap<>();
        resMap.put("label_name", crime);

        Preconditions.checkArgument(
                crime.split("_").length > 1, "wrong format input label string!");
        String crimeName = crime.split("_",2)[1];
        Preconditions.checkArgument(
                LabelKeyWords.gQueryLabelMap.containsKey(crimeName), "Not support input label string!");

        List<List<String>> matchList = LabelKeyWords.gQueryLabelMap.get(crimeName);
        List<String> l2List = matchList.get(0);
        List<String> matchStrList = matchList.get(1);

        for(String l2Name: l2List){
            Map<String, Object> labelMap = genLabelList(session, doc_id, l2Name, 1).get(0);
            if(genLabelCode(labelMap, matchStrList))
                resMap.put("value", true);
            else
                resMap.put("value", false);

        }
        return resMap;
    }

    public static List<Map<String,Object>> genLabelList(final Session session,String doc_id, String L2Name, int k){
        Preconditions.checkArgument(
                LabelKeyWords.L2Labels.contains(L2Name), "Not supported L2 query label!");
        Preconditions.checkArgument(k>=1, "wrong value for return list length!");
        List<Map<String,Object>> resList = new ArrayList<>();
        switch (L2Name){
            case "WXJS_behavior":
                resList = generateLabelScore(session, doc_id, LabelKeyWords.isWXJS_behavior);
                break;
            case "JTZS_behavior":
                resList = generateLabelScore(session, doc_id, LabelKeyWords.isJTZS_behavior);
                break;
            case "ZSFMYSZZDP_circums":
                resList = generateLabelScore(session, doc_id, LabelKeyWords.isZSFMYSZZDP_circumstance);
                break;
            case "ZSFMYSZZDP_behavior":
                resList = generateLabelScore(session, doc_id, LabelKeyWords.isZSFMYSZZDP_behavior);
                break;
            case "XXZS_circums":
                resList = generateLabelScore(session, doc_id, LabelKeyWords.isXXZS_circumstance);
                break;
            case "XXZS_harm":
                resList = generateLabelScore(session, doc_id, LabelKeyWords.isXXZS_harm);
                break;
            case "XXZS_behavior":
                resList = generateLabelScore(session, doc_id, LabelKeyWords.isXXZS_behavior);
                break;
            case "penalty_heavier":
                resList = generateLabelScore(session, doc_id, LabelKeyWords.isMeasure_heavier);
                break;
            case "penalty_ease":
                resList = generateLabelScore(session, doc_id, LabelKeyWords.isMeasure_ease);
                break;
            case "penalty_ease_circumstance":
                resList = generateLabelScore(session, doc_id, LabelKeyWords.isMeasure_easeCir);
                break;
            default:
                System.err.println("bad format query label string!");
                break;
        }
        return k <= resList.size() ? resList.subList(0,k): resList;

    }



    public static List<Map<String, Object>> genLabelListByCrimes(Session session,
                                                                String doc_id,
                                                                final Map<String, Object> MAX_SCORE_MAP,
                                                                final List<String> crimes){

        return LabelKeyWords.isAllLabels.entrySet().stream()
                .filter(entry -> crimes.contains(entry.getKey()))
                .map(entry -> {
                    Double score = queryFulltextContent(session, entry.getValue(), doc_id);
                    //Double maxScore = queryMaxScore(session, entry.getValue());
                    Double maxScore = (Double) MAX_SCORE_MAP.get(entry.getKey());
                    boolean val = score >= (0.6*maxScore);
                    HashMap<String, Object> map = new HashMap<>();
                    map.put("label_name", entry.getKey());
                    map.put("score", score);
                    map.put("max_score", maxScore);
                    map.put("value", val);
                    return map;
                })
                //.sorted(Comparator.comparing( (map -> (double)map.get("score")), Comparator.reverseOrder()))
                .collect(Collectors.toList());
    }

    // [["XXX": score1]]
    // [{"child_label_name":XXX", "rank":0, "score":XXX}]
    public static List<Map<String,Object>> generateLabelScore(final Session session,String doc_id, Map<String,String> labelMap){

        List<Map<String, Object>> scoreMapList = new ArrayList<>();
        labelMap.forEach((label, keyWord) -> {
            Double score = queryFulltextContent(session, keyWord, doc_id);
            Double maxScore = queryMaxScore(session, keyWord);
            Map<String, Object> scoreMap = new HashMap<>();
            scoreMap.put("label_name", label);
            scoreMap.put("score", score);
            scoreMap.put("max_score", maxScore);
            scoreMapList.add(scoreMap);
        });

        AtomicInteger index = new AtomicInteger(0);
        List<Map<String, Object>> sortedList = scoreMapList.stream()
                .sorted(Comparator.comparing( (map -> (double)map.get("score")), Comparator.reverseOrder()))
                .map( map -> {
                    map.put("rank", index.getAndIncrement());
                    return map; })
                .collect(Collectors.toList());

        return sortedList;
    }



    public static void addLabelContent(final Session session, Map<String,Object> prop) {
        String sql = " MERGE (c:LabelContent{doc_id: $prop.doc_id}) SET c = $prop";
        session.run(sql, ImmutableMap.of("prop", prop));
    }

    public static void deleteLabelContent(final Session session, String doc_id) {
        String sql = " MATCH (c:LabelContent{doc_id: $doc_id}) DELETE c";
        session.run(sql, ImmutableMap.of("doc_id", doc_id));
    }

    // USE LabelContentIndex for efficiency
    // ONLY need top 1
    public static Double queryFulltextContent(final Session session, String queryWord, String doc_id) {
        String sql = " CALL db.index.fulltext.queryNodes('LabelContentIndex', $queryWord) YIELD node, score" +
                " WITH node, score" +
                " WHERE node.doc_id = $doc_id " +
                " RETURN score ";
        StatementResult statementResult =  session.run(sql, ImmutableMap.of("queryWord", queryWord, "doc_id", doc_id));
        if (statementResult.hasNext()){
            return statementResult.single().get("score").asDouble();
        } else
            return 0.0;

    }

    public static Double queryMaxScore(final Session session, String queryWord) {
        String sql = " CALL db.index.fulltext.queryNodes('LabelContentIndex', $queryWord) YIELD node, score" +
                " RETURN max(score) as score ";
        StatementResult statementResult =  session.run(sql, ImmutableMap.of("queryWord", queryWord));
        if (statementResult.hasNext()){
            return statementResult.single().get("score").asDouble();
        } else
            return 0.0;

    }

    // parameter: single crimeStr
    // return true or false
    public static boolean genLabelCode(Map<String,Object> labelMap,  List<String> matchList){
        if(matchList.size() > 0){
            return matchList.contains(labelMap.get("child_label_name").toString()) && (Double)labelMap.get("score") > 0.1;
        } else {
            return (Double)labelMap.get("score") > 0.1;
        }
    }
}
